<?php
/**
 * Created by PhpStorm.
 * User: Administrator
 * Date: 2023/10/17
 * Time: 12:17
 */

namespace app\common\lib;

use app\model\douyin\DouyinRequestApiLog;
use support\Cache;

class DouYinOpenApp
{
    protected $domain = 'https://open.douyin.com/';
    protected $config = [];
    protected static $instance;

    public function __construct()
    {
        $this->config = config('param.open_douyin_app');

    }

    public static function getInstance()
    {
        //        if(is_object(self::$instance)){
//            return self::$instance;
//        }


        return new self();
    }

    public function getAccessTokenByCode($param)
    {
        $apiParam = [
            'code'=>$param['code'],
            'client_key'=>$this->config['client_key'],
            'client_secret'=>$this->config['client_secret'],
            'grant_type'=>'authorization_code',
        ];
        $header = [
            'Content-Type'=>'application/json'
        ];
        $res = Guzzle::post($this->domain,'oauth/access_token/',$apiParam,$header,'json');

        $this->insertLogData($this->domain.'oauth/access_token/',$apiParam,$res);
        $data = $res['data']??[];
        if(!empty($data['error_code'])){
            Common::sendDingDingNotice('根据code获取sccess_token失败:'."\n\r   ".json_encode($data,256));
            throw new IndexException(4020,'授权失败-'.$data['description']);
        }

        return $data;
    }

    public function getAccessToken($param)
    {
        $where = [];
        if(!empty($param['douyin_access_token_id'])){
            $where['douyin_access_token_id']=$param['douyin_access_token_id'];
        }elseif(!empty($param['open_id'])){
            $where['open_id']=$param['open_id'];
        }
        if($where==false){
            return false;
        }

        $where['platform'] = 2;
        $accessToken = DouyinAccessToken::getInstance()->getWhere($where)->orderBy('expires_time','desc')->first();
        if($accessToken==false){
            return false;
        }
        if($accessToken['expires_time']<time()){
            $accessToken = $this->refreshToken($accessToken);
        }elseif($accessToken['refresh_expires_time']<time()){
            $accessToken = $this->refreshRefreshToken($accessToken);
            $accessToken = $this->refreshToken($accessToken);
        }

        return $accessToken;
    }

    public function refreshToken($accessToken,$iscallback=false)
    {
        $apiParam = [
            'client_key'=>$this->config['client_key'],
            'grant_type'=>'refresh_token',
            'refresh_token'=>$accessToken['refresh_token'],
        ];
        $headers = [
            'Content-Type'=>'application/x-www-form-urlencoded'
        ];
        $result = Guzzle::post($this->domain,'oauth/refresh_token/',$apiParam,$headers,'fromData');
        $this->insertLogData($this->domain.'oauth/refresh_token/',$apiParam,$result);
        if(!empty($result['data']) && empty($result['data']['error_code'])){
            $accessToken->access_token = $result['data']['access_token'];
            $accessToken->expires_in = $result['data']['expires_in'];
            $accessToken->refresh_token = $result['data']['refresh_token'];
            $accessToken->refresh_expires_in = $result['data']['refresh_expires_in'];
            $accessToken->expires_time = time()+$result['data']['expires_in']-43200;
            $accessToken->refresh_expires_time = time()+$result['data']['refresh_expires_in']-43200;
            $accessToken->save();

            return $accessToken;
        }elseif(!empty($result['data']) && $result['data']['error_code']=='10010'){
            $accessToken = $this->refreshRefreshToken($accessToken);
            if($accessToken && $iscallback==false){
                return $this->refreshToken($accessToken,true);
            }
        }

        return false;
    }

    public function refreshRefreshToken($accessToken)
    {
        //刷新 refresh_token
        $apiParam = [
            'client_key'=>$this->config['client_key'],
            'refresh_token'=>$accessToken['refresh_token'],
        ];
        $headers = [
            'Content-Type'=>'application/x-www-form-urlencoded'
        ];
        $result = Guzzle::post($this->domain,'oauth/renew_refresh_token/',$apiParam,$headers,'fromData');
        $this->insertLogData($this->domain.'oauth/renew_refresh_token/',$apiParam,$result);
        if(!empty($result['data']) && empty($result['data']['error_code'])){
            $accessToken->refresh_expires_in = $result['data']['expires_in'];
            $accessToken->refresh_token = $result['data']['refresh_token'];
            $accessToken->refresh_expires_time = time()+$result['data']['expires_in']-43200;
            $accessToken->save();
            return $accessToken;
        }

        return false;
    }

    public function getUserData($param)
    {
        $accessToken = $this->getAccessToken($param);
        if($accessToken==false){
            return [];
        }

        //首先获取公开用户信息
        $apiParam = [
            'access_token'=>$accessToken['access_token'],
            'open_id'=>$accessToken['open_id'],
        ];

        $headers = [
            'Content-Type'=>'application/x-www-form-urlencoded'
        ];
        $apiUrl = $this->domain.'oauth/userinfo/';
        $result = Guzzle::post($this->domain,'oauth/userinfo/',$apiParam,$headers,'fromData');

        $this->insertLogData($apiUrl,$apiParam,$result);
        if(empty($result['data']['error_code'])){
            $mobile = '';
            if(!empty($result['data']['encrypt_mobile'])){
                $clientSecret = $this->config['client_secret'];
                $encryptedMobile = $result['data']['encrypt_mobile'];

                $iv = substr($clientSecret, 0, 16);
                $mobile = openssl_decrypt($encryptedMobile, 'aes-256-cbc', $clientSecret, 0, $iv);
            }
            $result['data']['mobile'] = $mobile;
        }

        return $result??[];
    }

    /**
     * 粉丝画像的粉丝数据
     * @param $param
     * @return array|mixed
     * @throws \Exception
     */
    public function getUserFansData($param)
    {
        $accessToken = $this->getAccessToken($param);
        if($accessToken==false){
            return [];
        }

        $apiParam = [
            'open_id'=>$accessToken['open_id'],
        ];

        $headers = [
            'Access-Token'=>$accessToken['access_token'],
            //'Content-Type'=>'application/x-www-form-urlencoded'
        ];

        $path = 'api/douyin/v1/user/fans_data/?'.http_build_query($apiParam);
        $apiUrl = $this->domain.$path;

        $result = Guzzle::get($this->domain,$path,$headers);

        $this->insertLogData($apiUrl,[],$result);

        return $result??[];
    }

    /**
     * 生成client_token
     * @param bool $isrefer 是否强制刷新
     * @return bool|\Illuminate\Contracts\Cache\Repository
     * @throws \Exception
     */
    public function getClientToken($isrefer=false)
    {
        $accessToken =  Cache::get('douyin_open_client_token');
        if(!empty($accessToken) && $isrefer==false){
            return $accessToken;
        }

        $apiParam = [
            'client_key'=>$this->config['client_key'],
            'client_secret'=>$this->config['client_secret'],
            'grant_type'=>'client_credential',
        ];
        $headers = [
            'Content-Type'=>'application/json'
        ];
        $result = Guzzle::post($this->domain,'oauth/client_token/',$apiParam,$headers,'json');
        $this->insertLogData($this->domain.'oauth/client_token/',$apiParam,$result);
        if(!empty($result['data']) && empty($result['data']['error_code'])){
            $accessToken = $result['data']['access_token'];
            Cache::set('douyin_open_client_token',$accessToken,$result['data']['expires_in']-300);
            return $accessToken;
        }

        return false;
    }

    /**
     * 创建团长
     * @param int $agentId
     * @return mixed
     * @throws \Exception
     */
    public function createAgent($agentId=0)
    {
        $apiParam = [
            'agent_id'=>$agentId,
            'agent_nickname'=>$this->config['agent_name']??'傻瓜推文',
        ];
        $headers = [
            'Content-Type'=>'application/json',
            'access-token'=>$this->getClientToken(),
        ];
        $result = Guzzle::post($this->domain,'api/match/v1/taskbox/save_agent/',$apiParam,$headers,'json');
        $this->insertLogData($this->domain.'api/match/v1/taskbox/save_agent/',$apiParam,$result);

        if($result && ($result['err_no']=='28001008' || $result['err_no']=='28001003')){
            $this->getClientToken(true);
            $result = $this->createAgent($agentId=0);
        }

        return $result;
    }

    /**
     * 获取合作链接
     * @param $agentId
     * @param $appid
     * @param $taskId
     * @param $userSn
     * @return mixed
     * @throws \Exception
     */
    public function getCooperateLink($agentId,$appid=null,$taskId=null,$userSn=null)
    {
        $apiParam = [
            'agent_id'=>$agentId,
            'agency_talent_uid'=>(string)($userSn??request()->curUserId),
        ];
        $appid && $apiParam['app_id'] = $appid;
        $taskId && $apiParam['task_id'] = $taskId;

        $headers = [
            'Content-Type'=>'application/json',
            'access-token'=>$this->getClientToken(),
        ];
        $result = Guzzle::post($this->domain,'api/match/v1/taskbox/gen_agent_link/',$apiParam,$headers,'json');
        $this->insertLogData($this->domain.'api/match/v1/taskbox/gen_agent_link/',$apiParam,$result);

        if($result && ($result['err_no']=='28001008' || $result['err_no']=='28001003')){
            $this->getClientToken(true);
            $result = $this->getCooperateLink($agentId,$appid,$taskId,$userSn);
        }

        return $result;
    }

    /**
     * 换绑团长
     * @param $douyinId
     * @param $oldAgentId
     * @param $newAgentId
     * @return mixed
     * @throws \Exception
     */
    public function changeAgent($douyinId,$oldAgentId,$newAgentId)
    {
        $apiParam = [
            'old_agent_id'=>$oldAgentId,
            'new_agent_id'=>$newAgentId,
            'douyin_id'=>$douyinId,
        ];
        $headers = [
            'Content-Type'=>'application/json',
            'access-token'=>$this->getClientToken(),
        ];
        $result = Guzzle::post($this->domain,'api/match/v1/taskbox/change_user_bind_agent/',$apiParam,$headers,'json');
        $this->insertLogData($this->domain.'api/match/v1/taskbox/change_user_bind_agent/',$apiParam,$result);

        if($result && ($result['err_no']=='28001008' || $result['err_no']=='28001003')){
            $this->getClientToken(true);
            $result = $this->changeAgent($douyinId,$oldAgentId,$newAgentId);
        }

        return $result;
    }

    /**
     * 查询绑定关系
     * @param $page
     * @param $pageSize
     * @param $agentId
     * @param $douyinId
     * @param $userSn
     * @return mixed
     * @throws \Exception
     */
    public function searchBindRecord($page=1,$pageSize=100,$agentId=null,$douyinId=null,$userSn=null)
    {
        $apiParam = [
            'page_no'=>$page,
            'page_size'=>$pageSize,
        ];
        $agentId && $apiParam['agent_id'] = (string)$agentId;
        $douyinId && $apiParam['douyin_id'] = (string)$douyinId;
        $userSn && $apiParam['agency_talent_uid'] = (string)$userSn;
        $headers = [
            'Content-Type'=>'application/json',
            'access-token'=>$this->getClientToken(),
        ];
        $result = Guzzle::post($this->domain,'api/match/v1/taskbox/get_agency_user_bind_record/',$apiParam,$headers,'json');
        $this->insertLogData($this->domain.'api/match/v1/taskbox/get_agency_user_bind_record/',$apiParam,$result);

        if($result && ($result['err_no']=='28001008' || $result['err_no']=='28001003')){
            $this->getClientToken(true);
            $result = $this->searchBindRecord($page,$pageSize,$agentId,$douyinId,$userSn);
        }

        return $result;
    }

    /**
     * 查询任务详情
     * @param $appid
     * @param $content
     * @param $type
     * @param $page
     * @param $pageSize
     * @return mixed
     * @throws \Exception
     */
    public function searchTaskInfo($appid,$content,$type=1,$page=1,$pageSize=100)
    {
        $apiParam = [
            'page_no'=>$page,
            'page_size'=>$pageSize,
            'appid'=>$appid,
            'query_params_type'=>$type,
            'query_params_content'=>(string)$content,
        ];

        $headers = [
            'Content-Type'=>'application/json',
            'access-token'=>$this->getClientToken(),
        ];
        $result = Guzzle::post($this->domain,'api/match/v1/taskbox/query_task_info/',$apiParam,$headers,'json');
        $this->insertLogData($this->domain.'api/match/v1/taskbox/query_task_info/',$apiParam,$result);
        if($result && is_array($result) && ($result['err_no']=='28001008' || $result['err_no']=='28001003')){
            $this->getClientToken(true);
            $result = $this->searchTaskInfo($appid,$content,$type,$page,$pageSize);
        }
        return $result;
    }

    /**
     * 获取任务ID
     * @param $appid
     * @param $startTime
     * @param $endTime
     * @return mixed
     * @throws \Exception
     */
    public function searchTaskId($appid,$startTime,$endTime)
    {
        $apiParam = [
            'appid'=>$appid,
            'create_start_time'=>$startTime,
            'create_end_time'=>$endTime,
        ];

        $headers = [
            'Content-Type'=>'application/json',
            'access-token'=>$this->getClientToken(),
        ];
        $result = Guzzle::post($this->domain,'api/match/v1/taskbox/query_app_task_id/',$apiParam,$headers,'json');
        $this->insertLogData($this->domain.'api/match/v1/taskbox/query_app_task_id/',$apiParam,$result);
        if($result && ($result['err_no']=='28001008' || $result['err_no']=='28001003')){
            $this->getClientToken(true);
            $result = $this->searchTaskId($appid,$startTime,$endTime);
        }
        return $result;
    }

    /**
     * 查询视频汇总数据
     * @param $page
     * @param $pageSize
     * @param $appid
     * @return mixed
     * @throws \Exception
     */
    public function searchVideoSumData($page=1,$pageSize=10000,$appid=null)
    {
        $apiParam = [
            'video_publish_start_time'=>strtotime(date('Y-m-d',strtotime('-1 month'))),
            'video_publish_end_time'=>time(),
            'page_no'=>$page,
            'page_size'=>$pageSize,
        ];
        $appid && $apiParam['app_id'] = $appid;
        $headers = [
            'Content-Type'=>'application/json',
            'access-token'=>$this->getClientToken(),
        ];
        $result = Guzzle::post($this->domain,'api/match/v1/taskbox/query_agency_video_sum_data/',$apiParam,$headers,'json');
        $this->insertLogData($this->domain.'api/match/v1/taskbox/query_agency_video_sum_data/',$apiParam,$result);

        if($result && ($result['err_no']=='28001008' || $result['err_no']=='28001003')){
            $this->getClientToken(true);
            $result = $this->searchBindRecord($page,$pageSize,$appid);
        }

        return $result;
    }

    /**
     * 查询视频每日明细数据
     * @param $billingData
     * @param $startTime
     * @param $endTime
     * @param $page
     * @param $pageSize
     * @param $appid
     * @return mixed
     * @throws \Exception
     */
    public function searchVideoDailyData($billingData,$startTime,$endTime,$page=1,$pageSize=10000,$appid=null)
    {
        $apiParam = [
            'video_publish_start_time'=>(string)$startTime,
            'video_publish_end_time'=>(string)$endTime,
            'billing_date'=>(string)$billingData,
            'page_no'=>$page,
            'page_size'=>$pageSize,
        ];
        $appid && $apiParam['app_id'] = (string)$appid;
        $headers = [
            'Content-Type'=>'application/json',
            'access-token'=>$this->getClientToken(),
        ];
        $result = Guzzle::post($this->domain,'api/match/v1/taskbox/query_agency_video_daily_data/',$apiParam,$headers,'json');
        $this->insertLogData($this->domain.'api/match/v1/taskbox/query_agency_video_daily_data/',$apiParam,$result);

        if($result && ($result['err_no']=='28001008' || $result['err_no']=='28001003')){
            $this->getClientToken(true);
            $result = $this->searchVideoDailyData($billingData,$startTime,$endTime,$page,$pageSize,$appid);
        }

        return $result;
    }

    /**
     * 查询推广计划视频数据下载链接
     * @param $billingDate
     * @return mixed
     * @throws \Exception
     */
    public function getVideoDailyDataDownLink($billingDate)
    {
        $apiParam = [
            'bill_date'=>(string)$billingDate,
        ];
        $headers = [
            'Content-Type'=>'application/json',
            'access-token'=>$this->getClientToken(),
        ];
        $result = Guzzle::post($this->domain,'api/match/v2/taskbox/agency_query_bill_link/',$apiParam,$headers,'json');
        $this->insertLogData($this->domain.'api/match/v2/taskbox/agency_query_bill_link/',$apiParam,$result);

        if($result && ($result['err_no']=='28001008' || $result['err_no']=='28001003')){
            $this->getClientToken(true);
            $result = $this->getVideoDailyDataDownLink($billingDate);
        }

        return $result;
    }

    protected function insertLogData($apiurl,$param,$result)
    {
        $insert = [
            'param'=>json_encode($param,256),
            'result'=>json_encode($result,256),
            'apiurl'=>$apiurl,
        ];

        return DouyinRequestApiLog::getInstance()->insertGetId($insert);
    }
}